<?php
/**
 * 参考:  https://www.jianshu.com/p/b5ee9f72fcd1
 * 如果对方上线了, 后端主动推送
 * 如果对户下线了, 后端主动推送
 */

namespace App\Console\Commands;


use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
use swoole_websocket_server;


/**
 * 主逻辑: 需求, 实现点对点聊天
 * 主要是处理 open,message,request,close 四个方法
 * open 和 close 如果不需要, 可以不用实现, 直接删除
 */
class Swoole extends Command
{
    #定义key, 保存fd和用户id的映射关系
    public $redis_key; //$fd = redis::get($this->redis_key);
    public $fromId;
    public $num = 0;
    public $ws;
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'swoole {action?}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'swoole';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $action = $this->argument('action');
        switch ($action) {
            case 'restart':
                $this->info('swoole server restarted');
                break;
            case 'close':
                $this->info('swoole server stoped');
                break;
            default:
                $this->start();
                break;
        }
    }

    public function start()
    {
        //创建websocket服务器对象，监听0.0.0.0:9501端口
        $this->ws = new swoole_websocket_server("0.0.0.0", 9501);

        #心跳检测 https://wiki.swoole.com/wiki/page/283.html
        $this->ws->set([
//            'worker_num' => 1, # 开启2个swoole进程,没有生效
            'heartbeat_check_interval' => 30, #心跳检测 每隔多少秒，遍历一遍所有的连接
            'heartbeat_idle_time' => 65, #心跳检测 最大闲置时间，超时触发close并关闭  默认为heartbeat_check_interval的2倍，两倍是容错机制，多一点是网络延迟的弥补; 如果客户端超过65秒没有向socket发送数据, 此链接会签字关闭
        ]);

        //监听WebSocket连接打开事件
        $this->ws->on('open', [$this, 'open']);

        //监听WebSocket消息事件
        $this->ws->on('message', [$this, 'message']);

        //监听WebSocket主动推送消息事件
        $this->ws->on('request', [$this, 'request']);

        //监听WebSocket连接关闭事件
        $this->ws->on('close', [$this, 'close']);

        $this->ws->start();
    }


    public function open($ws, $frame)
    {
        echo '有人偷偷连接我了, 我返回了fd: ' . $frame->fd ."\r\n";
        $ws->push(
            $frame->fd,
            json_encode([
                'type' => 'init',
                'client_fd' => $frame->fd,
            ])
        );

    }

    /**
     * 当客户端发来消息时触发
     * 接收消息
     * @param $frame
     */
    public function message($ws, $frame)
    {
        #获取客户端发送消息
        echo '我收到的消息数据是: '.$frame->data."\r\n";
        $message_data = json_decode($frame->data, true);
        if (!isset($message_data['type'])) {
            return;
        }
        switch ($message_data['type']) {
            case 'bind':
                // 推送给发送方
                $this->fromId = $message_data['fromId'];
                // 绑定用户id和fd 建立对应关系
                #绑定用户id
                $this->redis_key = 'socket_' . $this->fromId;
                Redis::set($this->redis_key, $frame->fd);
                echo '用户id: ' . $this->fromId."\r\n";
                return;
            case "online":
                // 推送给发送方
                $fromId = $message_data['from_id'];
                $toId = $message_data['to_id'];
                $status = redis::get('socket_' . $toId) == true ? '对方在线' : '对方不在线';
                $data = [
                    'type' => 'online',
                    'from_id' => $fromId,
                    'to_id' => $toId,
                    'status' => $status
                ];
                $ws->push(
                    redis::get('socket_' . $fromId),
                    json_encode($data)
                );
                return;
            case 'heart':
                // 推送给发送方
                #   在线, 返回心跳数据
                if (redis::get('socket_' . $message_data['fromId'])) {
                    $data['type'] = 'heart';
                    $data['data'] = '心跳一次';
                    $ws->push(
                        redis::get('socket_' . $this->fromId),
                        json_encode($data)
                    );
                }
                return;
            case 'say':
                // 推送给对方
                $text = nl2br(htmlspecialchars($message_data['data']));
                $fromId = $message_data['from_id'];
                $toId = $message_data['to_id'];
                $data = [
                    'type' => 'text',
                    'data' => $text,
                    'from_id' => $fromId,
                    'to_id' => $toId,
                    'time' => time()
                ];
                #   在线
                if (redis::get('socket_' . $toId)) {
                    $data['is_read'] = 1;
                    $data['tip_msg'] = '对方在线';
                    $ws->push(
                        redis::get('socket_' . $toId),
                        json_encode($data)
                    );
                } else {
                    #   不在线不推
                    $data['is_read'] = 0;
                    $data['tip_msg'] = '对方不在线';
                }
                return;
            case "img":
                // 推送给对方
                $fromId = $message_data['from_id'];
                $toId = $message_data['to_id'];
                $img_name = $message_data['data'];
                $data = [
                    'type' => 'img',
                    'from_id' => $fromId,
                    'to_id' => $toId,
                    'img_name' => $img_name
                ];
                if (redis::get('socket_' . $toId)) {
                    $ws->push(
                        redis::get('socket_' . $toId),
                        json_encode($data)
                    );
                } else {
                    // 不在线不推送
                }
                return;
            default:
//                echo '用户id: ' . $this->fromId . " 发送过来的数据有问题, 要跟后端沟通好";
                return;
        }
    }

    /**
     * 接收请求, php主动推送
     * @param $request
     * @param $response
     */
    public function request($request, $response)
    {
        $data = $request->post;
        $user_id = $data['to_id'];
        $fd = redis::get('socket_' . $user_id);
        if ($fd) {
            $this->ws->push($fd, json_encode($data));
        }
    }

    /**
     * 关闭连接
     * @param $ws
     * @param $fd
     */
    public function close($ws, $fd)
    {
        echo 'fd: ' . $fd . " 下线了,go, go , go \r\n";
        echo '用户id: ' . $this->fromId . " 下线了,go, go , go \r\n";
        redis::del($this->redis_key);
    }
}
